package rtcf.framework
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.events.NetStatusEvent;
	import flash.events.StatusEvent;
	
	import rtcf.*;

	/** 
	 * Uses an RTCF Connection factory to get an RTCF connection and connect/login to a server-side RTCF application. 
	 * Acts as an RTCFComponent factory. 
	 */
	public class RTCFApplication extends EventDispatcher{
		
		public static const DEFAULT_STATE:String = "default";
		public static const CONNECTING_STATE:String = "connecting";
		public static const CONNECTED_STATE:String = "connected";
		public static const DISCONNECTED_STATE:String = "disconnected"; // Must have been connected. 
		public static const FAILED_STATE:String = "failed"; // Failed to connect.
		
		// Current application state.
		private var applicationState:String = DEFAULT_STATE; 
		// components is an RMI ID hash of components created by componentVar:RTCFComponent = rtcfApplication.getComponent("rmi_id");
		private var components:Object = {};
		// componentDefinitions provides information about each component that is needed to create each one.
		// If a definition doesn't exist the component cannot be created.
		private var componentDefinitions:XMLList;
		// For connecting to an application instance:
		private var connectionFactory:RTCFConnectionFactory;
		// The all important current connection:
		public var rtcfConnection:RTCFConnection;
		public var clientInformation:Object;
		private var _configXML:XML;
		
		
		public function RTCFApplication(target:IEventDispatcher=null){
			super(target);
		}
		
		public function config(configXML:XML, applicationID:String = null, instanceName:String = null):void{
			var tempConfig:XML;
			if(typeof configXML != "xml"){
				trace("ERROR: parameter configXML not passed in or not of type xml.");
			}
			// Get the RTCFApplication tag by applicationID or the first one.
			if (applicationID){
				tempConfig = configXML.RTCFApplications.RTCFApplication.(@id == applicationID)[0];
			}
			else {
				tempConfig = configXML.RTCFApplications.RTCFApplication[0];
			}
			
			// Once we have the right RTCFApplication tag, assign a COPY of it to _configXML
			if (tempConfig.hasComplexContent()){
				_configXML = tempConfig.copy();
			}
			else {
				trace("ERROR: Can't extract config XML with application id: " + applicationID + " from: " + configXML);
				return;
			}
			
			// If the user wants to overrwite the instance name, here is where it happens:
			if (instanceName){
				_configXML.@instance = instanceName;
				//trace("with instance name update: " + _configXML);
			}
			else {
				//trace("no instance name update: " + _configXML);
			}
			
			componentDefinitions = _configXML.components;
			
			connectionFactory = new RTCFConnectionFactory();
			connectionFactory.config = _configXML;
			connectionFactory.addEventListener("connectResult", connectResult);
			connectionFactory.addEventListener("connectFault",  connectFault);			
		}
		
		/**
		 * 
		 */
		private function changeState(stateName:String, info:Object):void{
			info.stateName = stateName;
			var previousState:String = applicationState;
			var isChange:Boolean = applicationState == stateName;
			applicationState = stateName;
			//dispatchEvent( new StatusEvent("stateChange", false, false, applicationState, "") );
			var ns:NetStatusEvent = new NetStatusEvent("stateChange",false, false, info);
			dispatchEvent( ns );
			if (isChange){
				var connectionState:String = (applicationState == CONNECTED_STATE) ? CONNECTED_STATE : DISCONNECTED_STATE; 
				if (applicationState == CONNECTED_STATE || previousState == CONNECTED_STATE){
					var newInfo:Object;
					for(var p:String in info) newInfo[p] = info[p];
					newInfo.stateName = connectionState;
					ns = new NetStatusEvent("connectionStatus",false, false, newInfo);
					dispatchEvent( ns );
				}  
			}
		}
		
		// Thinking this is a bad idea...
		public function connectComponents(...components):void{
			
			for (var i:int = 0; i < components.length; i++){
				//trace(i + ": " + components[i]);
				var component:Object = components[i];
				if (typeof component.applicationStateChange == "function"){
					addEventListener("connectionStatus", component.connectionStatus);
				}
			}
			if (components.length > 0){
				var connectionState:String = (applicationState == CONNECTED_STATE) ? CONNECTED_STATE : DISCONNECTED_STATE; 
				dispatchEvent( new StatusEvent("connectionStatus", false, false, connectionState, "") );
			}
		}
		
		/**
		 * When the connection succeeds decorate the connection.
		 */
		private function connectResult(event:NetStatusEvent):void{
			rtcfConnection = connectionFactory.currentConnection;
			rtcfConnection.addEventListener("disconnectFault", disconnectFault);
			// Create a client object with properites owner that points to this app, s2c, and _$_ for initing.		
			rtcfConnection.client = {owner: this, info: event.info};
			rtcfConnection.client.s2c = function (msg:String):*{
				trace("rtcfConnection.client.s2c> arguements: " + arguments + "\n");
				var objectName:String = arguments[0];
				var methodName:String = arguments[1];
				var params:Array = [];
				for (var i:int = 2; i < arguments.length; i++){
					params.push(arguments[i]);
				}
				trace("rtcfConnection.client.s2c> " + objectName + "." + methodName + "(" + params.join(",") + ")");
				return params.join(",");
			}
			rtcfConnection.client._$_ = function (msg:Object):void{
				trace("rtcfConnection.client._$_> arguments: " + arguments);
				this.owner.connectionMessage(msg);
			}	
			// No notifications for components here.
		}
		
		private function connectionMessage(init:Object):void{
			clientInformation = init;
			//trace("connectionMessage> init: " + init);
			for ( var p:String in init) trace("connectionMessage> " + p + ": " + init[p]);
			changeState(CONNECTED_STATE, rtcfConnection.client.info);
		}
		
		private function connectFault(event:NetStatusEvent):void{
			// Dispose of the connection? and notify any components.
			changeState(FAILED_STATE, event.info);
			//trace("connectFault> " + event);
		}	
		
		private function disconnectFault(event:NetStatusEvent):void{
			changeState(DISCONNECTED_STATE, event.info);
		}		
		
			
		public function getComponent(componentRMI_ID:String):RTCFComponent{
			// If the component already exists in cache return it
			var rtcfComponent:RTCFComponent = components[componentRMI_ID];
			if (rtcfComponent){
				return rtcfComponent;
			}
			
			// Is the component's id in the configXML? 
			var componentDefinition:XMLList = componentDefinitions.component.(@id==componentRMI_ID); 
			if (! componentDefinition.hasSimpleContent()) {
				// The component is not defined in the configXML!!
				trace("The component with id " + componentRMI_ID + " is not defined in the configXML!!");
				return null;
			}
			
			// If it doesn't exist in cache create it using it's configXML definition
			rtcfComponent = new RTCFComponent(componentRMI_ID); // Notice that we may not be connected so don't have a netconnection to offer the component
			rtcfComponent.config = componentDefinition[0];
			rtcfComponent.rtcfApplication = this;
			components[componentRMI_ID] = rtcfComponent;
			
			
			
			return rtcfComponent;
		}
	
		public function connect(credentials:Credentials):void{
			var buffer:String = "Connect with credentials: \n";
			for (var p:String in credentials) buffer += "credentials." +  p + ": " + credentials[p] + "\n";
			//trace(buffer);
			connectionFactory.connect(credentials);
		}
		
		public function close():void{
			//trace("Close" );
			if (rtcfConnection) rtcfConnection.close();
		}		
		
	}
}